//------------------------------------------------------------------------------
// Copyright (c) 2008 www.dnaide.com
// Licensed under the MIT (MIT-LICENSE.txt)
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.Util;

using DNAide.Core;
using DNAide.IO;

namespace DNAide.Web
{
	/// <summary>
	/// </summary>
	public abstract class DNAideStaticSiteMapProvider : SiteMapProvider
	{

		internal string AppDomainAppVirtualPathWithTrailingSlash =
			VirtualPathUtility.AppendTrailingSlash(HttpRuntime.AppDomainAppVirtualPath);

		internal static SiteMapNodeCollection Empty =
			new SiteMapNodeCollection();

		internal object _lock = new object();

		// Table maps nodes to their child xmlNode collections
		private Hashtable _childNodeCollectionTable;
		internal IDictionary ChildNodeCollectionTable
		{
			get
			{
				if (_childNodeCollectionTable == null)
				{
					lock (_lock)
					{
						if (_childNodeCollectionTable == null)
						{
							_childNodeCollectionTable = new Hashtable();
						}
					}
				}

				return _childNodeCollectionTable;
			}
		}

		// Case sensitive table that maps key to sitemap xmlNode.
		private Hashtable _keyTable;
		internal IDictionary KeyTable
		{
			get
			{
				if (_keyTable == null)
				{
					lock (_lock)
					{
						if (_keyTable == null)
						{
							_keyTable = new Hashtable();
						}
					}
				}

				return _keyTable;
			}
		}

		// Table maps nodes to their parent nodes
		private Hashtable _parentNodeTable;
		internal IDictionary ParentNodeTable
		{
			get
			{
				if (_parentNodeTable == null)
				{
					lock (_lock)
					{
						if (_parentNodeTable == null)
						{
							_parentNodeTable = new Hashtable();
						}
					}
				}

				return _parentNodeTable;
			}
		}

		// Case insensitive table that maps url to sitemap xmlNode.
		private Hashtable _urlTable;
		internal IDictionary UrlTable
		{
			get
			{
				if (_urlTable == null)
				{
					lock (_lock)
					{
						if (_urlTable == null)
						{
							_urlTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
						}
					}
				}

				return _urlTable;
			}
		}


		/// <devdoc>
		///    <para>Add single xmlNode to provider tree and sets the parent-child relation.</para>
		/// </devdoc>
		protected override void AddNode(SiteMapNode node, SiteMapNode parentNode)
		{
			if (node == null)
			{
				throw new ArgumentNullException("node");
			}

			lock (_lock)
			{
				bool validUrl = false;

				string url = node.Url;
				if (!String.IsNullOrEmpty(url))
				{
					if (AppDomainAppVirtualPathWithTrailingSlash != null)
					{

						if (!PathAide.IsAbsolutePhysicalPath(url))
						{
							url = VirtualPathUtility.Combine(AppDomainAppVirtualPathWithTrailingSlash, url);

							// Normalize url
							url = VirtualPathUtility.ToAbsolute(url);
						}
						
						if (UrlTable[url] != null)
							throw new InvalidOperationException(
								StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MultipleNodesWithIdenticalUrl, url));
					}

					validUrl = true;
				}

				String key = node.Key;
				Debug.Assert(key != null);
				if (KeyTable.Contains(key))
				{
					throw new InvalidOperationException(
						StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MultipleNodesWithIdenticalKey, key));
				}

				KeyTable[key] = node;

				if (validUrl)
				{
					UrlTable[url] = node;
				}

				if (parentNode != null)
				{
					ParentNodeTable[node] = parentNode;
					if (ChildNodeCollectionTable[parentNode] == null)
						ChildNodeCollectionTable[parentNode] = new SiteMapNodeCollection();

					((SiteMapNodeCollection)ChildNodeCollectionTable[parentNode]).Add(node);
				}
			}
		}

		/// <summary>
		/// </summary>
		/// <returns></returns>
		public abstract SiteMapNode BuildSiteMap();

		/// <summary>
		/// </summary>
		protected virtual void Clear()
		{
			lock (_lock)
			{
				if (_childNodeCollectionTable != null)
					_childNodeCollectionTable.Clear();

				if (_urlTable != null)
					_urlTable.Clear();

				if (_parentNodeTable != null)
					_parentNodeTable.Clear();

				if (_keyTable != null)
					_keyTable.Clear();
			}
		}

		/// <summary>
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public override SiteMapNode FindSiteMapNodeFromKey(String key)
		{
			SiteMapNode result = base.FindSiteMapNodeFromKey(key);

			if (result == null)
			{
				result = (SiteMapNode)KeyTable[key];
			}

			return ReturnNodeIfAccessible(result);
		}

		/// <summary>
		/// </summary>
		/// <param name="rawUrl"></param>
		/// <returns></returns>
		public override SiteMapNode FindSiteMapNode(String rawUrl)
		{
			if (rawUrl == null)
				throw new ArgumentNullException("rawUrl");

			// URL needs to be trimmed.
			rawUrl = rawUrl.Trim();

			if (rawUrl.Length == 0)
			{
				return null;
			}

			string virtualPath = rawUrl;

			int qs = rawUrl.IndexOf('?');
			if (qs != -1)
			{
				virtualPath = rawUrl.Substring(0, qs);
			}

			// Make sure it is an app absolute url
			if (VirtualPathUtility.IsAppRelative(virtualPath))
			{
				virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
			}

			if (qs != -1)
			{
				virtualPath += rawUrl.Substring(qs);
			}

			BuildSiteMap();

			return ReturnNodeIfAccessible((SiteMapNode)UrlTable[virtualPath]);
		}

		
		/// <summary>
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
		{
			if (node == null)
				throw new ArgumentNullException("node");

			BuildSiteMap();
			SiteMapNodeCollection collection = (SiteMapNodeCollection)ChildNodeCollectionTable[node];

			if (collection == null)
			{
				SiteMapNode childNodeFromKey = (SiteMapNode)KeyTable[node.Key];
				if (childNodeFromKey != null)
				{
					collection = (SiteMapNodeCollection)ChildNodeCollectionTable[childNodeFromKey];
				}
			}

			if (collection != null)
			{
				if (!SecurityTrimmingEnabled)
				{
					return SiteMapNodeCollection.ReadOnly(collection);
				}

				HttpContext context = HttpContext.Current;
				SiteMapNodeCollection trimmedCollection = new SiteMapNodeCollection(collection.Count);
				foreach (SiteMapNode subNode in collection)
				{
					if (subNode.IsAccessibleToUser(context))
					{
						trimmedCollection.Add(subNode);
					}
				}

				return SiteMapNodeCollection.ReadOnly(trimmedCollection);
			}

			return DNAideStaticSiteMapProvider.Empty;
		}

		/// <summary>
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public override SiteMapNode GetParentNode(SiteMapNode node)
		{
			if (node == null)
				throw new ArgumentNullException("node");

			BuildSiteMap();
			SiteMapNode parent = (SiteMapNode)ParentNodeTable[node];

			if (parent == null)
			{
				// Try to find the original xmlNode in the table using the key
				SiteMapNode fallbackNode = (SiteMapNode)KeyTable[node.Key];
				if (fallbackNode != null)
				{
					parent = (SiteMapNode)ParentNodeTable[fallbackNode];
				}
			}

			// Try the parent providers.
			if (parent == null && ParentProvider != null)
			{
				parent = ParentProvider.GetParentNode(node);
			}

			return ReturnNodeIfAccessible(parent);
		}

		/// <summary>
		/// </summary>
		/// <param name="node"></param>
		protected override void RemoveNode(SiteMapNode node)
		{
			if (node == null)
				throw new ArgumentNullException("node");

			lock (_lock)
			{
				SiteMapNode oldParent = (SiteMapNode)ParentNodeTable[node];
				if (ParentNodeTable.Contains(node))
					ParentNodeTable.Remove(node);

				if (oldParent != null)
				{
					SiteMapNodeCollection collection = (SiteMapNodeCollection)ChildNodeCollectionTable[oldParent];
					if (collection != null && collection.Contains(node))
						collection.Remove(node);
				}

				string url = node.Url;
				if (url != null && url.Length > 0 && UrlTable.Contains(url))
				{
					UrlTable.Remove(url);
				}

				string key = node.Key;
				if (KeyTable.Contains(key))
				{
					KeyTable.Remove(key);
				}
			}
		}

		internal static SiteMapNode ReturnNodeIfAccessible(SiteMapNode node)
		{
			if (node != null && node.IsAccessibleToUser(HttpContext.Current))
			{
				return node;
			}

			return null;
		}

	}
}
